
/**
*	\file
*	Defines the sun::window class.
*	\author Sean Tromnas
*/

#include <sun_pre_compiled.hpp>
#include <sun/window/window.hpp>
#include <sun/internal/system.hpp>
#include <sun/gl.hpp>

#include <Windows.h>
#include <WindowsX.h>

namespace sun
{

	/** Private, platform specific implementation of the window class. */
	struct window::impl
	{
		impl( )
		:		m_window_handle( 0 )
			,	m_device_context( 0 )
			,	m_render_context( 0 )

			,	m_cursor_locked( false )
		{
			memset( &m_window_region, 0, sizeof(RECT) );
		}

		window::window_settings			m_settings;
		window::focus_signal_type		m_focus_changed_signal;
		window::closed_signal_type		m_quit_signal;
		window::analogue_signal_type	m_analogue_signal;
		window::button_signal_type		m_button_signal;

		HWND	m_window_handle;
		HDC		m_device_context;
		HGLRC	m_render_context;
		RECT	m_window_region;
		bool	m_cursor_locked;

		struct raw_input_mouse_button_event
		{
			USHORT bitfeild;
			buttons::enum_ button;
			bool pressed;
		};

		static LRESULT CALLBACK window_proc( HWND window_handle, UINT message, WPARAM w_param, LPARAM l_param )
		{
			window::impl * implementation = reinterpret_cast<window::impl*>( GetWindowLongPtrA( window_handle, GWLP_USERDATA ) );
			bool handled = false;

			if ( implementation != nullptr )
			{
				switch ( message )
				{
					case WM_MOVE:
					{
						//LONG width = implementation->m_window_region.right - implementation->m_window_region.left;
						//LONG height = implementation->m_window_region.bottom - implementation->m_window_region.top;

						//implementation->m_window_region.left = static_cast<LONG>( LOWORD(l_param) );
						//implementation->m_window_region.top = static_cast<LONG>( HIWORD(l_param) );

						//implementation->m_window_region.right = implementation->m_window_region.left + width;
						//implementation->m_window_region.bottom = implementation->m_window_region.top + height;

						//	Yeah fuck that. The documentation seems to imply that the x and y values sent are absolute screen positions.
						//	But in practice they seem to be delta values.
						//	So to be safe I'm just getting what windows says the position is instead.

						GetWindowRect( implementation->m_window_handle, &(implementation->m_window_region) );
					}
					break;

					case WM_GETMINMAXINFO:
					{
						MINMAXINFO * pMinmaxinfo = reinterpret_cast<MINMAXINFO *>( l_param );
			
						pMinmaxinfo->ptMaxTrackSize.x = static_cast<LONG>(implementation->m_settings.width);
						pMinmaxinfo->ptMaxTrackSize.y = static_cast<LONG>(implementation->m_settings.height);

						pMinmaxinfo->ptMinTrackSize.x = static_cast<LONG>(implementation->m_settings.width);
						pMinmaxinfo->ptMinTrackSize.y = static_cast<LONG>(implementation->m_settings.height);

						handled = true;
					}
					break;

					case WM_ACTIVATE:
					{
						handled = implementation->m_focus_changed_signal( w_param != WA_INACTIVE );
					}
					break;

					case WM_CLOSE:
					case WM_QUIT:
					{
						implementation->m_quit_signal( );
						handled = true;	// force the window to stay open.
					}
					break;

					case WM_INPUT:
					{
						static const UINT max_buffer_size = 48;
						static BYTE raw_input_buffer[max_buffer_size];

						UINT buffer_size = 0;
						GetRawInputData(	reinterpret_cast<HRAWINPUT>( l_param ),
											RID_INPUT,
											nullptr,
											&buffer_size,
											sizeof(RAWINPUTHEADER) );

						//nmn::output_message( "%1% %2% %3% %4% %5%", sizeof( RAWMOUSE ), sizeof( RAWKEYBOARD ), sizeof( RAWHID ), sizeof(RAWINPUTHEADER), buffer_size );
						NMN_ASSERT_MESSAGE( buffer_size <= max_buffer_size, "Input buffer larger than allouance." );

						GetRawInputData(	reinterpret_cast<HRAWINPUT>( l_param ),
											RID_INPUT,
											raw_input_buffer,
											&buffer_size,
											sizeof(RAWINPUTHEADER) );

						RAWINPUT * raw_input = reinterpret_cast<RAWINPUT *>( raw_input_buffer );
						switch( raw_input->header.dwType )
						{
							case RIM_TYPEMOUSE:
							{
								RAWMOUSE const & mouse = raw_input->data.mouse;
								if (	( mouse.usFlags & MOUSE_MOVE_ABSOLUTE ) == 0
									&&	( mouse.usFlags & MOUSE_VIRTUAL_DESKTOP ) == 0
									&&	( mouse.usFlags & MOUSE_ATTRIBUTES_CHANGED ) == 0 )	// raw input is retarded.
								{
									float delta_x = static_cast<float>( mouse.lLastX );
									float delta_y = static_cast<float>( mouse.lLastY );

									handled |= implementation->m_analogue_signal( analogues::mouse_delta_x, delta_x );
									handled |= implementation->m_analogue_signal( analogues::mouse_delta_y, delta_y );
								}

								static impl::raw_input_mouse_button_event button_events[] =
								{
									{ RI_MOUSE_LEFT_BUTTON_DOWN, buttons::left_mouse, true },
									{ RI_MOUSE_LEFT_BUTTON_UP, buttons::left_mouse, false },

									{ RI_MOUSE_MIDDLE_BUTTON_DOWN, buttons::middle_mouse, true },
									{ RI_MOUSE_MIDDLE_BUTTON_UP, buttons::middle_mouse, false },

									{ RI_MOUSE_RIGHT_BUTTON_DOWN, buttons::right_mouse, true },
									{ RI_MOUSE_RIGHT_BUTTON_UP, buttons::right_mouse, false },

									{ RI_MOUSE_BUTTON_4_DOWN, buttons::x_1_mouse, true },
									{ RI_MOUSE_BUTTON_4_UP, buttons::x_1_mouse, false },

									{ RI_MOUSE_BUTTON_5_DOWN, buttons::x_2_mouse, true },
									{ RI_MOUSE_BUTTON_5_UP, buttons::x_2_mouse, false },

									{ 0, buttons::number_of, false },
								};

								for ( auto iter = button_events; iter->bitfeild != 0; ++iter )
								{
									if ( ( mouse.usButtonFlags & iter->bitfeild ) == iter->bitfeild )
									{
										handled |= implementation->m_button_signal( iter->button, iter->pressed );
									}
								}

								if ( ( mouse.usButtonFlags & RI_MOUSE_WHEEL ) == RI_MOUSE_WHEEL )
								{
									float delta_scroll = static_cast<float>( static_cast<signed short>( mouse.usButtonData ) );
									handled |= implementation->m_analogue_signal( analogues::mouse_delta_scroll, delta_scroll );
								}
							}
							break;

							case RIM_TYPEKEYBOARD:
							{
								RAWKEYBOARD const & keyboard = raw_input->data.keyboard;

								static const size_t max_windows_keys = 255;
								static sun::buttons::enum_ vkey_to_sun_button[max_windows_keys] =
								{
									buttons::number_of,																									//	0

									buttons::left_mouse,	buttons::right_mouse,	buttons::number_of,		buttons::middle_mouse,	buttons::x_1_mouse,	//	5
									buttons::x_2_mouse,		buttons::number_of,		buttons::backspace,		buttons::tab,			buttons::number_of,	//	10
									buttons::number_of,		buttons::clear,			buttons::return_,		buttons::number_of,		buttons::number_of,	//	15
									buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::pause,			buttons::caps_lock,	//	20
									buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,	//	25
									buttons::number_of,		buttons::escape,		buttons::number_of,		buttons::number_of,		buttons::number_of,	//	30
									buttons::number_of,		buttons::space,			buttons::page_up,		buttons::page_down,		buttons::end,		//	35
									buttons::home,			buttons::left,			buttons::up,			buttons::right,			buttons::down,		//	40
									buttons::number_of,		buttons::print,			buttons::number_of,		buttons::sys_req,		buttons::insert,	//	45
									buttons::delete_,		buttons::help,			buttons::num_0,			buttons::num_1,			buttons::num_2,		//	50

									buttons::num_3,			buttons::num_4,			buttons::num_5,			buttons::num_6,			buttons::num_7,		//	55
									buttons::num_8,			buttons::num_9,			buttons::number_of,		buttons::number_of,		buttons::number_of,	//	60
									buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::a,			//	65
									buttons::b,				buttons::c,				buttons::d,				buttons::e,				buttons::f,			//	70
									buttons::g,				buttons::h,				buttons::i,				buttons::j,				buttons::k,			//	75
									buttons::l,				buttons::m,				buttons::n,				buttons::o,				buttons::p,			//	80
									buttons::q,				buttons::r,				buttons::s,				buttons::t,				buttons::u,			//	85
									buttons::v,				buttons::w,				buttons::x,				buttons::y,				buttons::z,			//	90

									buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,	//	95
									buttons::keypad_0,		buttons::keypad_1,		buttons::keypad_2,		buttons::keypad_3,		buttons::keypad_4,	//	100
									buttons::keypad_5,		buttons::keypad_6,		buttons::keypad_7,		buttons::keypad_8,		buttons::keypad_9,	//	105
									buttons::keypad_multiply,buttons::keypad_plus,	buttons::number_of,		buttons::keypad_minus,	buttons::keypad_period,	//	110
									buttons::keypad_divide,	buttons::function_1,	buttons::function_2,	buttons::function_3,	buttons::function_4,	//	115
									buttons::function_5,	buttons::function_6,	buttons::function_7,	buttons::function_8,	buttons::function_9,	//	120
									buttons::function_10,	buttons::function_11,	buttons::function_12,	buttons::function_13,	buttons::function_14,	//	125
									buttons::function_15,	buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,	//	130
									buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,	//	135
									buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,	//	140
									buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::num_lock,		buttons::scroll_lock,	//	145
									buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,	//	150

									buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,	//	155
									buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::number_of,		buttons::left_shift,	//	160
									buttons::right_shift,	buttons::left_ctrl,		buttons::right_ctrl,	buttons::left_alt,		buttons::right_alt,	//	165

									buttons::number_of,
								};

								if ( vkey_to_sun_button[ keyboard.VKey ] != buttons::number_of )
								{
									bool pressed = ( keyboard.Flags & RI_KEY_BREAK ) == 0;
									implementation->m_button_signal( vkey_to_sun_button[ keyboard.VKey ], pressed );
								}
							}
							break;
						}
					}
					break;
				}
			}

			return handled ? 0 : DefWindowProc( window_handle, message, w_param, l_param );
		}
	};

	//
	//	Public Member Functions
	//

	/**
	*	Default constructor.
	*	\author Sean Tromans
	*/
	window::window( )
		:		m_impl( new window::impl( ) )
	{
	}

	/**
	*	Deconstructor.
	*	\author Sean Tromans
	*/
	window::~window( )
	{
	}

	/**
	*	Attempts to create the application window with the desired settings.
	*	@param settings The settings to use when creating the window.
	*	@return false if the window could not be created.
	*	\author Sean Tromans
	*/
	bool window::create_window( window_settings const & settings )
	{
		//	nmn::log creating window

		m_impl->m_settings = settings;

		HINSTANCE const & instance_handle = system_config::get_singleton( )->get_param<HINSTANCE>( "instance_handle" );

		//	Define the size of the window to create
		RECT window_size = 
		{
			0, 0,
			static_cast<LONG>(settings.width), static_cast<LONG>(settings.height),
		};

		//	Define a window "class"
		WNDCLASS window_class;
		window_class.cbClsExtra = 0;
		window_class.cbWndExtra = 0;
		window_class.hbrBackground = 0;
		window_class.hCursor = LoadCursor(0, IDC_ARROW);
		window_class.hIcon = 0;
		window_class.hInstance = instance_handle;
		window_class.lpfnWndProc = &impl::window_proc;
		window_class.lpszClassName = L"sun_window";
		window_class.lpszMenuName = 0;
		window_class.style = CS_OWNDC;
		if ( 0 == RegisterClass( &window_class ) )
		{
			//	nmn::log RegisterClass failed
			return false;
		}

		//	Set full screen mode
		if ( m_impl->m_settings.fullscreen )
		{
			//	nmn::log setting full screen

			DEVMODE dev_mode_screen_settings;
			memset( &dev_mode_screen_settings, 0, sizeof(dev_mode_screen_settings) );
			dev_mode_screen_settings.dmSize = sizeof(dev_mode_screen_settings);
			dev_mode_screen_settings.dmPelsWidth = window_size.right;
			dev_mode_screen_settings.dmPelsHeight = window_size.bottom;
			dev_mode_screen_settings.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;

			if (	ChangeDisplaySettings(	&dev_mode_screen_settings,
											CDS_FULLSCREEN ) !=	DISP_CHANGE_SUCCESSFUL)
			{
				if (	MessageBox(	NULL,
									L"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?",
									L"Real-Time Graphics (RTG)",
									MB_YESNO | MB_ICONEXCLAMATION	) == IDYES )
				{
					//	nmn::log full screen not possible
					m_impl->m_settings.fullscreen = false;
				}
				else
				{
					//	nmn::log full screen not possible, initialise failed
					return false;
				}
			}
		}

		//	Define window style
		DWORD display_window_ex_style = WS_EX_APPWINDOW;
		DWORD display_window_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN;

		if ( m_impl->m_settings.fullscreen )
		{
			display_window_style |= WS_POPUP;
		}
		else
		{
			display_window_ex_style	|= WS_EX_WINDOWEDGE;
			//display_window_style	|= WS_OVERLAPPEDWINDOW;
			display_window_style |= (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX );
		}

		AdjustWindowRectEx(	&window_size, display_window_style, FALSE, display_window_ex_style );
		memcpy( &(m_impl->m_window_region), &window_size, sizeof(RECT) );

		// Create a window of our window "class"
		m_impl->m_window_handle = CreateWindowExA(	display_window_ex_style,
													"sun_window",
													m_impl->m_settings.title.c_str( ),
													display_window_style,
													CW_USEDEFAULT, CW_USEDEFAULT,
													window_size.right - window_size.left,
													window_size.bottom - window_size.top,
													0,
													0,
													instance_handle,
													0 );
		if ( m_impl->m_window_handle == 0 )
		{
			//	nmn::log failed ot create window
			return false;
		}

		//	Get the device context
		m_impl->m_device_context = GetDC( m_impl->m_window_handle );

		// Define a pixel format you wish for OpenGL to use
		PIXELFORMATDESCRIPTOR pfd;
		memset(&pfd, 0, sizeof(pfd));
		pfd.nSize = sizeof(pfd);
		pfd.nVersion = 1;
		pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER | PFD_DRAW_TO_WINDOW;
		pfd.iPixelType = PFD_TYPE_RGBA;
		pfd.cColorBits =	static_cast<BYTE>( m_impl->m_settings.colour_bits );
		pfd.cDepthBits =	static_cast<BYTE>( m_impl->m_settings.depth_bits );
		pfd.cStencilBits =	static_cast<BYTE>( m_impl->m_settings.stencil_bits );

		// Select the best available match of pixel format for what you're wanting
		int pixel_format = ChoosePixelFormat( m_impl->m_device_context, &pfd );
		if ( pixel_format == 0 )
		{
			//	nmn::log could not find a pixel format
			return false;
		}

		// Make that the pixel format of the window
		if ( !SetPixelFormat( m_impl->m_device_context, pixel_format, &pfd ) )
		{
			//	nmn::log could not apply pixel format
			return false;
		}

		// Create and bind an OpenGL rendering context
		m_impl->m_render_context = wglCreateContext( m_impl->m_device_context );
		wglMakeCurrent( m_impl->m_device_context, m_impl->m_render_context );

		// Load WGL extensions to gain access to improved pixel format functions
		if ( glewInit( ) != GLEW_OK )
		{
			//	nmn::log could not initialise OpenGL
			return false;
		}

		// Release the rendering context, device context, and destroy the window
		wglMakeCurrent( 0, 0 );
		wglDeleteContext( m_impl->m_render_context );
		ReleaseDC( m_impl->m_window_handle, m_impl->m_device_context );
		DestroyWindow( m_impl->m_window_handle );

		// Create a window of our window "class"
		// This is because we can only set the pixel format of a window once.
		m_impl->m_window_handle = CreateWindowExA(	display_window_ex_style,
													"sun_window",
													m_impl->m_settings.title.c_str( ),
													display_window_style,
													CW_USEDEFAULT, CW_USEDEFAULT,
													window_size.right - window_size.left,
													window_size.bottom - window_size.top,
													0,
													0,
													instance_handle,
													0 );
		if ( m_impl->m_window_handle == 0 )
		{
			//	nmn::log failed ot create window
			return false;
		}

		//	Get the device context
		m_impl->m_device_context = GetDC( m_impl->m_window_handle );

		// Define desired attributes for the new pixel format
		int	int_attributes[] = 
		{
			WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
			WGL_ACCELERATION_ARB,	WGL_FULL_ACCELERATION_ARB,
			WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
			WGL_DOUBLE_BUFFER_ARB,	GL_TRUE,
			WGL_PIXEL_TYPE_ARB,		WGL_TYPE_RGBA_ARB,
			WGL_COLOR_BITS_ARB,		m_impl->m_settings.colour_bits,
			WGL_DEPTH_BITS_ARB,		m_impl->m_settings.depth_bits,
			WGL_STENCIL_BITS_ARB,	m_impl->m_settings.stencil_bits,
			WGL_SAMPLE_BUFFERS_ARB,	m_impl->m_settings.samples > 1 ? GL_TRUE : GL_FALSE,
			WGL_SAMPLES_ARB,		m_impl->m_settings.samples > 1 ? m_impl->m_settings.samples : 1,
			0,						0,
		};

		float float_attributes[] =
		{
			0.0f, 0.0f,
		};

		// Select a suitable pixel format
		UINT number_of_formats = 0;
		if (	!wglChoosePixelFormatARB(	m_impl->m_device_context,
											int_attributes,
											float_attributes,
											1,
											&pixel_format,
											&number_of_formats ) )
		{
			//	nmn::log could not find suitable pixel format
			return false;
		}

		// Make that the pixel format of the window
		if ( DescribePixelFormat( m_impl->m_device_context, pixel_format, sizeof(pfd), &pfd ) == 0 )
		{
			//	nmn::log could not describe pixel format
			return false;
		}

		if ( !SetPixelFormat( m_impl->m_device_context, pixel_format, &pfd ) )
		{
			//	nmn::log could not apply pixel format
			return false;
		}

		// Create and bind a new OpenGL rendering context
		m_impl->m_render_context = wglCreateContext( m_impl->m_device_context );
		wglMakeCurrent( m_impl->m_device_context, m_impl->m_render_context );

		//	Check if the required minimum OpenGL capabilities are supported on this PC.
		if (	glewInit( ) != GLEW_OK
			||	!GLEW_VERSION_2_1
			||	!GLEW_ARB_framebuffer_object
			||	!GLEW_EXT_gpu_shader4 )
		{
			//	nmn::log the minimum required openGL extensions are not supported.
			return false;
		}

		//	Check if the additional, user required, extensions are supported by this PC.
		size_t supported_extensions = 0;
		std::for_each(	m_impl->m_settings.extensions.begin( ),
						m_impl->m_settings.extensions.end( ),
						[&supported_extensions]( std::string const & extension )->void
		{
			if ( glewIsSupported( extension.c_str( ) ) )
			{
				++supported_extensions;
			}
			else
			{
				// nmn::log extension is not supported
			}
		} );

		if ( supported_extensions != m_impl->m_settings.extensions.size( ) )
		{
			//	nmn::log the required additional extensions are not supported.
			return false;
		}

		//	Register to receive mouse and keyboard input.
		RAWINPUTDEVICE raw_input_devices[2] =
		{
			{
				1,						//usUsagePage. 1 = generic
				6,						//usUsage. 6 = keyboard
				RIDEV_DEVNOTIFY,		//dwFlags. RIDEV_NOLEGACY = no WM_KEYDOWN etc, RIDEV_DEVNOTIFY = notify hot plugging
				m_impl->m_window_handle	//hwndTarget.
			},

			{
				1,						//1 = generic
				2,						//2 = mouse
				RIDEV_DEVNOTIFY,		//RIDEV_NOLEGACY = no WM_KEYDOWN etc, RIDEV_DEVNOTIFY = notify hot plugging
				m_impl->m_window_handle	//
			}
		};

		if ( RegisterRawInputDevices( raw_input_devices, 2, sizeof(RAWINPUTDEVICE) ) == FALSE )
		{
			//	nmn::log could not get keyboard and mouse input
			return false;
		}

		//	Register this class with the window so we can get it back later during the message loop.
		SetWindowLongPtrA( m_impl->m_window_handle, GWLP_USERDATA, reinterpret_cast<LONG_PTR>( m_impl.get( ) ) );
		ShowWindow( m_impl->m_window_handle, SW_SHOW );
		SetActiveWindow( m_impl->m_window_handle );

		//	nmn::log window created successfully
		return true;
	}

	/**
	Register a new slot to be notified when the window gains or loses focus.
	@param slot The new slot to attach to the signal.
	\author Sean Tromans
	*/
	window::signal_connection_type window::notify_focus_change( window::focus_signal_type::slot_type const & slot )
	{
		return m_impl->m_focus_changed_signal.connect( slot );
	}

	/**
	Register a new slot to be notified when the user closes the window.
	@param slot The new slot to attach to the signal.
	\author Sean Tromans
	*/
	window::signal_connection_type window::notify_quit( window::closed_signal_type::slot_type const & slot )
	{
		return m_impl->m_quit_signal.connect( slot );
	}

	/**
	Register a new slot to be notified when an analogue input is received.
	@param slot The new slot to attach to the signal.
	\author Sean Tromans
	*/
	window::signal_connection_type window::notify_analogue_changed( window::analogue_signal_type::slot_type const & slot )
	{
		return m_impl->m_analogue_signal.connect( slot );
	}

	/**
	Register a new slot to be notified whenever a button is pressed or released.
	@param slot The new slot to attach to the signal.
	\author Sean Tromans
	*/
	window::signal_connection_type window::notify_button_changed( window::button_signal_type::slot_type const & slot )
	{
		return m_impl->m_button_signal.connect( slot );
	}

	/**
	Enables or disables the default cursor.
	@param show Whether the cursor should be active or not.
	\author Sean Tromans
	*/
	void window::show_cursor( bool show )
	{
		ShowCursor( show ? TRUE : FALSE );
	}

	/**
	Locks the cursor to stay within this window.
	@param lock Whether the cursor should be locked or not.
	\author Sean Tromans
	*/
	void window::grab_cursor( bool lock )
	{
		m_impl->m_cursor_locked = lock;
		ClipCursor( lock ? &(m_impl->m_window_region) : nullptr );
	}

	/**
	*	Translates all the currently waiting system messages.
	*	\author Sean Tromans
	*/
	void window::pump_messages( )
	{
		MSG msg;

		// Handle all pending events
		while ( PeekMessage( &msg, m_impl->m_window_handle, 0, 0, PM_REMOVE ) != 0 )
		{
			grab_cursor( m_impl->m_cursor_locked );	// silly windows.
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}
	}

	/**
	Refreshes the window with the most recently drawn image.
	\author Sean Tromans
	*/
	void window::present( ) const
	{
		SwapBuffers( m_impl->m_device_context );
	}

	/**
	*	Destroys the window cleanly to either exit the application, or to recreate another window.
	*	\author Sean Tromans
	*/
	void window::close_window( )
	{
		//	nmn::log window closing

		//	Hide the window
		ShowWindow( m_impl->m_window_handle, SW_HIDE );

		//	Close OpenGL
		wglMakeCurrent( 0, 0 );
		wglDeleteContext( m_impl->m_render_context );

		//	Destroy the window
		ReleaseDC( m_impl->m_window_handle, m_impl->m_device_context );
		DestroyWindow( m_impl->m_window_handle );
		
		//	Tidy
		m_impl->m_window_handle = 0;
		m_impl->m_render_context = 0;
		m_impl->m_device_context = 0;
	}

};
